
#ifndef ROPUFU_MULTI_SENSOR_INFORMATION_INFORMATION_AT_MOST_HPP_INCLUDED
#define ROPUFU_MULTI_SENSOR_INFORMATION_INFORMATION_AT_MOST_HPP_INCLUDED

#include "../more_math.hpp"

#include <aftermath/template_math.hpp>

#include <algorithm>
#include <array>
#include <cstdint>
#include <ostream>
#include <string>

namespace ropufu
{
    template <std::size_t t_count_streams, std::size_t t_count_affected>
    struct information_at_most_iterator
    {
        static const std::size_t count_streams = t_count_streams;
        static const std::size_t count_affected = t_count_affected;
        typedef information_at_most_iterator<count_streams, count_affected> type;

    private:
        std::size_t m_exact_size = 0;
        std::array<bool, count_streams> m_position;

    public:
        explicit information_at_most_iterator(const std::array<bool, count_streams>& position)
            : m_position(position)
        {
            for (std::size_t i = 0; i < count_streams; i++) if (position[i]) this->m_exact_size++;
        }

        static type first()
        {
            std::array<bool, count_streams> position;
            position.fill(false);
            position[count_streams - 1] = true;
            return type(position);
        }

        static type last()
        {
            std::array<bool, count_streams> position;
            position.fill(false);
            return type(position);
        }

        bool operator !=(const type& other) const
        {
            return this->m_position != other.m_position;
        }

        const std::array<bool, count_streams>& operator *() const
        {
            return this->m_position;
        }

        const type& operator ++()
        {
            if (!std::next_permutation(this->m_position.begin(), this->m_position.end()))
            {
                if (this->m_exact_size == count_affected)
                {
                    this->m_exact_size = 0;
                    this->m_position.fill(false);
                }
                else
                {
                    this->m_exact_size++;
                    std::fill(this->m_position.begin() + (count_streams - this->m_exact_size), this->m_position.end(), true);
                }
            }
            return *this;
        }
    };

    template <std::size_t t_count_streams, std::size_t t_count_affected>
    struct information_at_most
    {
        static const std::size_t count_streams = t_count_streams;
        static const std::size_t count_affected = t_count_affected;
        typedef information_at_most<count_streams, count_affected> type;
        typedef information_at_most_iterator<count_streams, count_affected> iterator_type;

        std::size_t cardinality() const
        {
            return choose_at_most<count_streams, count_affected>::value;
        }

        iterator_type begin() const
        {
            return iterator_type::first();
        }

        iterator_type end() const
        {
            return iterator_type::last();
        }

        friend std::ostream& operator <<(std::ostream& os, const type&)
        {
            return os << "info_at_most_" << count_affected << "_of_" << count_streams;
        }
    };
}

#endif // ROPUFU_MULTI_SENSOR_INFORMATION_INFORMATION_AT_MOST_HPP_INCLUDED
